package com.googlecode.afdbaccess.web.common.login;

import java.security.acl.Group;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;

import org.jboss.security.SimpleGroup;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import com.googlecode.afdbaccess.cache.DbCache;

@RunWith(MockitoJUnitRunner.class)
public class StrongHashLoginModuleTest {
    @Test
    public void testInitializeHappy() {
        // Given
        final String dsJndiNameOptionValue = "dsJndiNameValue";
        final String dsJndiNameOptionKey = "dsJndiName";

        DataSource dsMock = Mockito.mock(DataSource.class);

        StrongHashLoginModule strongHashLoginModuleSpy = Mockito.spy(new StrongHashLoginModule());
        BDDMockito.willReturn(dsMock).given(strongHashLoginModuleSpy).getDataSource(dsJndiNameOptionValue);

        // Method parameters
        Subject subjectMock = new Subject(); // Can't mock a final class, so using the real one
        
        CallbackHandler callbackHandlerMock = Mockito.mock(CallbackHandler.class);

        @SuppressWarnings("unchecked")
        Map<String, ?> sharedStateMock = Mockito.mock(Map.class);

        @SuppressWarnings("unchecked")
        Map<String, Object> optionsMock = Mockito.mock(Map.class);
        BDDMockito.given(optionsMock.get(dsJndiNameOptionKey)).willReturn(dsJndiNameOptionValue);

        // When
        strongHashLoginModuleSpy.initialize(subjectMock, callbackHandlerMock, sharedStateMock, optionsMock);

        // Then
        Mockito.verify(strongHashLoginModuleSpy).getDataSource(dsJndiNameOptionValue);
    }

    @Test
    public void getUsersPassword() throws LoginException {
        // Given
        final String expectedReturned = "thePasswordReturned";
        final String source = "getUsersPassword()";

        new TestTaskCall<String>() {
            @Override
            protected String runTheMethodToTest(StrongHashLoginModuleMock strongHashLoginModuleSpy) throws LoginException {
                return strongHashLoginModuleSpy.getUsersPassword();
            }
        }.test(PasswordFetcher.class, expectedReturned, source);
    }

    @Test
    public void getRoleSetsInternal() throws LoginException {
        // Given
        final Group[] expectedReturned = new Group[] { new SimpleGroup("1"), new SimpleGroup("2") };
        final String source = "getRoleSetsInternal()";

        new TestTaskCall<Group[]>() {
            @Override
            protected Group[] runTheMethodToTest(StrongHashLoginModuleMock strongHashLoginModuleSpy) throws LoginException {
                return strongHashLoginModuleSpy.getRoleSetsInternal("theUserName");
            }
        }.test(UserInfoFetcher.class, expectedReturned, source);
    }
    
    //

    abstract class TestTaskCall<Return> {
        public void test(Class<? extends TransactedTask<Return>> theClass, Return expectedReturned, String source) throws LoginException {
            // Given
            String username = "theUserName";

            TransactedTask<Return> fetcherMock = Mockito.mock(theClass);
            BDDMockito.given(fetcherMock.performWithErrorHandlingAndCleanup(Matchers.any(DbCache.class), Matchers.eq(source), Matchers.eq(username))).willReturn(expectedReturned);

            
            StrongHashLoginModuleMock strongHashLoginModuleSpy = Mockito.spy(getStrongHashLoginModuleMock(fetcherMock));
            BDDMockito.willReturn(username).given(strongHashLoginModuleSpy).getUsername();

            // When
            Return actualReturned = runTheMethodToTest(strongHashLoginModuleSpy);

            // Then
            Mockito.verify(fetcherMock).performWithErrorHandlingAndCleanup(Matchers.any(DbCache.class), Matchers.eq(source), Matchers.eq(username));
            Mockito.verifyNoMoreInteractions(fetcherMock);

            Assert.assertEquals(expectedReturned, actualReturned);
        }

        private StrongHashLoginModuleMock getStrongHashLoginModuleMock(TransactedTask<Return> fetcherMock) {
            if(fetcherMock instanceof UserInfoFetcher) {
                return new StrongHashLoginModuleMock((UserInfoFetcher)fetcherMock);
            } else {
                return new StrongHashLoginModuleMock((PasswordFetcher)fetcherMock);
            }
        }

        abstract protected Return runTheMethodToTest(StrongHashLoginModuleMock strongHashLoginModuleSpy) throws LoginException;
    }

    //

    private class StrongHashLoginModuleMock extends StrongHashLoginModule {
        protected StrongHashLoginModuleMock(PasswordFetcher passwordFetcher) {
            super(passwordFetcher, null);
        }

        protected StrongHashLoginModuleMock(UserInfoFetcher userInfoFetcher) {
            super(null, userInfoFetcher);
        }

        // make the method getUserName() visible for Mockito to spy on
        @Override
        protected String getUsername() {
            return super.getUsername();
        }        
    }

}
